package practice;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

import com.google.common.base.Stopwatch;

import static practice.AssertUtils.ensureNotNull;

public class Main {

	enum MapType {
		HASHMAP, TREEMAP
	}

	public static void main(String[] args) throws InterruptedException {

		int numMillion = 1;
		int size = numMillion * 1000000;
		Stopwatch stopWatch = Stopwatch.createStarted();

		Map<String, String> map;
		MapType mapType = MapType.HASHMAP;
		if (mapType == MapType.HASHMAP)
			map = new HashMap<>(size);
		else
			map = new TreeMap<>();

		for (long i = 0; i < size; i++) {
			map.put("sathya" + i, i + "");
		}
		
		System.out.println("num elements: " + map.size());
		long elapsed = stopWatch.elapsed(TimeUnit.NANOSECONDS);
		System.out.println("Avg time for map.put() for " + mapType + " = "
				+ elapsed / map.size() + " nanoseconds. " + "(" + numMillion
				+ " million elements added in " + elapsed + " nanoseconds");

		printTimeElapsedForMapRetrieve(map);

		printMemoryUsage(map);
	}

	private static void printMemoryUsage(Map<String, String> map) {
		Runtime runtime = Runtime.getRuntime();
		Stopwatch stopWatch = Stopwatch.createStarted();
		runtime.gc();
		long elapsed = stopWatch.elapsed(TimeUnit.MILLISECONDS);
		long usedMemory = (runtime.totalMemory() - runtime.freeMemory());

		System.out.println("Avg memory per element = " + usedMemory
				/ map.size() + " bytes. total used memory: " + usedMemory
				+ " bytes. gc took [" + elapsed + "] milliseconds");				
	}

	private static void printTimeElapsedForMapRetrieve(Map<String, String> map) {

		final int NUM_OBSERVATIONS = 20;
		long totalElapsed = 0;
		List<String> values = new ArrayList<>();
		for (int i = 0; i < NUM_OBSERVATIONS; i++) {
			String key = "sathya" + new Random().nextInt(map.size());
			Stopwatch stopWatch = Stopwatch.createStarted();
			String value = map.get(key);
			long elapsed = stopWatch.elapsed(TimeUnit.NANOSECONDS);
			totalElapsed += elapsed;

			// System.out.println("key = " + key + ", value = " + value +
			// ", elapsed (nanoseconds) " + elapsed);
			values.add(value);
		}

		long avg = totalElapsed / values.size();
		System.out.println("avg time for map.get(): " + avg
				+ " nanoseconds (averaged over " + values.size()
				+ " observations)");
	}

	private void printTimeElapsedAssertVsNullCheck() {
		int random = new Random(new Date().getTime()).nextInt(10000);
		String s = random > 0 ? "sathya" : null;

		Stopwatch stopWatch = Stopwatch.createStarted();
		ensureNotNull(s, "sathya is null");
		long elapsed = stopWatch.elapsed(TimeUnit.NANOSECONDS);
		System.out.println("ensure completed in [" + elapsed + "] ns");

		stopWatch = Stopwatch.createStarted();
		ensureNotNull(s, "sathya is null");
		elapsed = stopWatch.elapsed(TimeUnit.NANOSECONDS);
		System.out.println("ensure completed in [" + elapsed + "] ns");

		stopWatch = Stopwatch.createStarted();
		ensureNotNull(s, "sathya is null");
		elapsed = stopWatch.elapsed(TimeUnit.NANOSECONDS);
		System.out.println("ensure completed in [" + elapsed + "] ns");

		stopWatch = Stopwatch.createStarted();
		ensureNotNull(s, "sathya is null");
		elapsed = stopWatch.elapsed(TimeUnit.NANOSECONDS);
		System.out.println("ensure completed in [" + elapsed + "] ns");

		boolean isNull = false;
		stopWatch = Stopwatch.createStarted();
		if (s == null)
			isNull = true;
		elapsed = stopWatch.elapsed(TimeUnit.NANOSECONDS);
		System.out.println("obj == null check completed in [" + elapsed
				+ "] ns");
		System.out.println("isNull = " + isNull);
	}
}
